package org.dandsoft.blackbox;

import java.util.ArrayList;
import java.util.LinkedList;

/**
 * User: dvilyuzhanin
 * Date: 16.01.2009 14:55:19
 */
public class ChannelQueue {

    private final Object queueLock = new Object();
    private LinkedList<Frame> queue = new LinkedList<Frame>();
    private OutputSignal outputSignal = new Outputer();
    private InputSignal intputSignal = new Inputer();

    public InputSignal getInputChannel() {
        return intputSignal;
    }

    public OutputSignal getOutputChannel() {
        return outputSignal;
    }

    class Frame {
        private ArrayList<Signal> signals;
        private int currentIndex;

        public synchronized ArrayList<Signal> getSignals() {
            return signals;
        }

        public synchronized void setSignals(ArrayList<Signal> signals) {
            this.signals = signals;
        }

        public synchronized int getCurrentIndex() {
            return currentIndex;
        }

        public synchronized void setCurrentIndex(int currentIndex) {
            this.currentIndex = currentIndex;
        }
    }

    class Outputer implements OutputSignal {
        private Frame newFrame;

        public synchronized void startFrame() throws ChannelException {
            if (isFrameStarted()) {
                finishFrame();
            }
            newFrame = new Frame();
        }

        public synchronized void finishFrame() throws ChannelException {
            if (newFrame == null) {
                throw new ChannelException("Frame not started");
            } else {
                synchronized (queueLock) {
                    queue.add(newFrame);
                    newFrame = null;
                }
            }
        }

        public synchronized boolean isFrameStarted() throws ChannelException {
            return newFrame != null;
        }

        public synchronized void close() throws ChannelException {
            if (isFrameStarted()) {
                finishFrame();
            }
        }

        public synchronized void output(ObjectId id, Value value) throws ChannelException {
            output(new Signal(id, value));
        }

        public synchronized void output(Signal signal) throws ChannelException {
            if (newFrame == null) {
                throw new ChannelException("Frame not started");
            } else {
                if (newFrame.getSignals() == null) {
                    newFrame.setSignals(new ArrayList<Signal>());
                }
                newFrame.getSignals().add(signal);
            }
        }

        public void output(Signal[] signals) throws ChannelException {
            output(signals, 0, signals.length);
        }

        public synchronized void output(Signal[] signals, int begin, int length) throws ChannelException {
            if (newFrame == null) {
                throw new ChannelException("Frame not started");
            } else {
                if (newFrame.getSignals() == null) {
                    newFrame.setSignals(new ArrayList<Signal>(length));
                }

                for (int i = begin; i < begin + length; i++) {
                    newFrame.getSignals().add(signals[i]);
                }
            }
        }
    }

    class Inputer implements InputSignal {
        private Frame currentFrame;
        private int currentIndex;

        public synchronized boolean isFrame() throws ChannelException {
            return currentFrame != null;
        }

        public synchronized boolean hasNextFrame() throws ChannelException {
            synchronized (queueLock) {
                return !queue.isEmpty();
            }
        }

        public synchronized boolean next() throws ChannelException {
            currentFrame = null;
            synchronized (queueLock) {
                currentFrame = queue.poll();
                currentIndex = -1;
            }
            return isFrame();
        }

        public void finishFrame() throws ChannelException {
            currentFrame = null;
        }

        public void close() throws ChannelException {
            currentFrame = null;
        }

        public synchronized Signal read() throws ChannelException {
            if (currentFrame == null) {
                throw new ChannelException("Frame not found");
            } else {
                currentIndex++;
                if (currentIndex < currentFrame.getSignals().size()) {
                    return currentFrame.getSignals().get(currentIndex);
                } else {
                    return null;
                }
            }
        }

        public int read(Signal[] buffer) throws ChannelException {
            return read(buffer, 0, buffer.length);
        }

        public synchronized int read(Signal[] buffer, int begin, int length) throws ChannelException {
            int count = 0;
            for (int i = begin; i < begin + length; i++) {
                Signal signal = read();
                if (signal != null) {
                    buffer[i] = signal;
                    count++;
                } else {
                    break;
                }
            }
            return count;
        }
    }
}
